package pe.edu.upc.dsd.whereeat.base.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Repository;

import pe.edu.upc.dsd.whereeat.base.dao.BaseDAO;
import pe.edu.upc.dsd.whereeat.base.domain.BaseEntity;

@Repository
@SuppressWarnings("unchecked")
public abstract class BaseDAOImpl<T extends BaseEntity, ID extends Serializable>
		implements BaseDAO<T, ID> {

	private Logger logger = Logger.getLogger(BaseDAOImpl.class);

	private Class<T> entityClass;
	@PersistenceContext
	private EntityManager entityManager;

	public BaseDAOImpl() {
		this.entityClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	public Class<T> getEntityClass() {
		return entityClass;
	}

	@PersistenceContext
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	public void save(T entity) throws Exception {
		// TODO Auto-generated method stub
		try {
			entityManager.persist(entity);
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public void update(T entity) throws Exception {
		// TODO Auto-generated method stub
		try {
			entityManager.merge(entity);
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public void delete(T entity) throws Exception {
		// TODO Auto-generated method stub
		try {
			entityManager.remove(entity);
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public T findById(ID id) throws Exception {
		// TODO Auto-generated method stub
		try {
			return (T) entityManager.find(getEntityClass(), id);
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public List<T> findAll() throws Exception {
		// TODO Auto-generated method stub
		try {
			Object[] params = {};
			return executeQuery("SELECT t FROM "
					+ getEntityClass().getSimpleName() + " t", null, null,
					params);
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public T findSingleObject(String queryString, Object... params)
			throws Exception {
		// TODO Auto-generated method stub
		try {
			List<T> list = executeQuery(queryString, null, null, params);
			if (list.size() != 1 || list.get(0) == null) {
				throw new Exception("Multiply or no entries.");
			}

			return list.get(0);
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public List<T> executeQuery(String queryString) throws Exception {
		return executeQuery(queryString, null, null, null);
	}

	public List<T> executeQuery(String queryString, Object... params)
			throws Exception {
		return executeQuery(queryString, null, null, params);
	}

	public List<T> executeQuery(String queryString, Integer startPosition,
			Integer maxResult, Object... params) throws Exception {
		// TODO Auto-generated method stub
		Query query = entityManager.createQuery(queryString);
		query.setHint("org.hibernate.cacheable", true);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}

		if (startPosition != null) {
			query.setFirstResult(startPosition);
		}

		if (maxResult != null) {
			query.setMaxResults(maxResult);
		}

		try {
			return (List<T>) query.getResultList();
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	public List<T> executeQuery(String queryString, Map<String, Object> params)
			throws Exception {
		// TODO Auto-generated method stub
		Query query = entityManager.createQuery(queryString);
		if (params != null && !params.isEmpty()) {
			for (String key : params.keySet()) {
				query.setParameter(key, params.get(key));
			}
		}

		try {
			return (List<T>) query.getResultList();
		} catch (Exception e) {
			// TODO: handle exception
			throw new Exception(e);
		}
	}

	protected Map<String, Object> createQueryString(String entityName,
			String propertyName, Map<String, Object> attr) {
		// TODO Auto-generated method stub
		String queryString = "SELECT x"
				+ ((propertyName != null && !"".equals(propertyName)) ? "."
						+ propertyName : "") + " FROM " + entityName + " x";
		String whereString = "";
		Object[] params = null;
		if (attr != null && !attr.isEmpty()) {
			List<Object> paramsList = new ArrayList<Object>();

			Set<String> keys = attr.keySet();
			for (String key : keys) {
				if (!"".equals(whereString)) {
					whereString = whereString + " AND";
				}
				whereString = whereString + " x." + key + " = ?";

				paramsList.add(attr.get(key));
			}
			params = new Object[paramsList.size()];
			params = paramsList.toArray(params);
		}
		if (!"".equals(whereString)) {
			queryString = queryString + " WHERE" + whereString;
		}

		Map<String, Object> result = new HashMap<String, Object>();
		result.put("queryString", queryString);
		result.put("params", params);
		return result;
	}

	@SuppressWarnings("rawtypes")
	protected Map<String, Object> createParameterizedQueryString(
			String entityName, String propertyName, Map<String, Object> attr) {
		// TODO Auto-generated method stub
		String queryString = "SELECT x"
				+ ((propertyName != null && !"".equals(propertyName)) ? "."
						+ propertyName : "") + " FROM " + entityName + " x";
		String whereString = "";
		Map<String, Object> params = null;
		if (attr != null && !attr.isEmpty()) {
			params = new HashMap<String, Object>();

			for (String key : attr.keySet()) {
				if (!"".equals(whereString)) {
					whereString = whereString + " AND";
				}

				if (attr.get(key) instanceof Map) {
					Map range = (Map) attr.get(key);
					Object[] keys = range.keySet().toArray();
					if (keys.length != 2) {
						continue;
					}

					String fromKey = (String) keys[0];
					String thruKey = (String) keys[1];
					if (!fromKey.startsWith("from")
							|| !thruKey.startsWith("thru")) {
						fromKey = (String) keys[1];
						thruKey = (String) keys[0];
					}
					whereString = whereString + " x." + key + " BETWEEN :"
							+ fromKey + " AND :" + thruKey;
					params.put(fromKey, range.get(fromKey));
					params.put(thruKey, range.get(thruKey));
				} else {
					whereString = whereString + " x." + key + " = :" + key;
					params.put(key, attr.get(key));
				}
			}
		}
		if (!"".equals(whereString)) {
			queryString = queryString + " WHERE" + whereString;
		}

		Map<String, Object> result = new HashMap<String, Object>();
		result.put("queryString", queryString);
		result.put("params", params);
		return result;
	}

	public List<Map<String, Object>> executeNativeQuery(String queryString,
			Integer startPosition, Integer maxResult, String[] resultFields,
			Object... params) throws Exception {
		// TODO Auto-generated method stub
		Query query = entityManager.createNativeQuery(queryString);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}

		if (startPosition != null) {
			query.setFirstResult(startPosition);
		}

		if (maxResult != null) {
			query.setMaxResults(maxResult);
		}

		try {
			List<Object[]> list = query.getResultList();
			List<Map<String, Object>> returnList = new ArrayList<Map<String, Object>>();
			for (Object[] obj : list) {
				Map<String, Object> map = new HashMap<String, Object>();
				int i = 0;
				for (String field : resultFields) {
					map.put(field, obj[i++]);
				}
				returnList.add(map);
			}
			return returnList;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			throw new Exception(e);
		}
	}

	public int executeNativeQuery(String queryString, Object... params)
			throws Exception {
		// TODO Auto-generated method stub
		Query query = entityManager.createNativeQuery(queryString);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}
		try {
			return query.executeUpdate();

		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			throw new Exception(e);
		}
	}

}
